package com.craftworks.tiles.impl;

import com.craftworks.tiles.AbstractTile;

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.util.Arrays;
import java.util.List;

public class TrackTile extends AbstractTile
{
    private static final Color PLOT_COLOR = Color.decode("#5390d9");
    private static final int PLOT_COUNT = 40;
    private static final int PLOT_WIDTH = 4;
    private static final int PLOT_GAP = 6;
    private static final int X_AXIS_SIZE = PLOT_GAP * (PLOT_COUNT + 1);
    private static final int Y_AXIS_SIZE = 70;

    private double lowBound;
    private double highBound;
    private double fps;
    private long lastTick;
    private int count;
    private double[] plots;
    private Color plotColor;

    private final double[] plots1, plots2;
    private final Timer timer;

    public TrackTile()
    {
        super(Size.WIDE_LANDSCAPE);
        lowBound = 0;
        highBound = 100;
        fps = 1.0;
        lastTick = 0;
        plots1 = new double[PLOT_COUNT + 1];
        plots2 = new double[PLOT_COUNT + 1];
        plots = plots1;
        count = 0;
        plotColor = PLOT_COLOR;

        timer = new Timer((int) (100 / fps), this::refresh);
    }

    @Override
    public List<String> propertyKeys()
    {
        return List.of("bound.low", "bound.high", "refresh.tps", "refresh.interval", "plot.value", "plot.color");
    }

    @Override
    public void setProperty(String key, Object value)
    {
        switch (key)
        {
            case "bound.high" ->
            {
                switch (value)
                {
                    case null -> setHighBound(lowBound + 100);
                    case Number n -> setHighBound(n.intValue());
                    case String s -> setHighBound(Integer.parseInt(s));
                    default -> throw new IllegalStateException("Unexpected value: " + value);
                }
            }
            case "bound.low" ->
            {
                switch (value)
                {
                    case null -> setLowBound(lowBound + 10);
                    case Number n -> setLowBound(n.intValue());
                    case String s -> setLowBound(Integer.parseInt(s));
                    default -> throw new IllegalStateException("Unexpected value: " + value);
                }
            }
            case "refresh.tps" ->
            {
                switch (value)
                {
                    case null -> setFps(1);
                    case String s -> setFps(Double.parseDouble(s));
                    case Number n -> setFps(n.doubleValue());
                    default -> throw new IllegalStateException("Unexpected value: " + value);
                }
            }
            case "refresh.interval" ->
            {
                switch (value)
                {
                    case null -> setFps(1);
                    case String s -> setFps(1 / Double.parseDouble(s));
                    case Number n -> setFps(1 / n.doubleValue());
                    default -> throw new IllegalStateException("Unexpected value: " + value);
                }
            }
            case "plot.value" ->
            {
                switch (value)
                {
                    case null -> setValue(lowBound);
                    case String s -> setValue(Double.parseDouble(s));
                    case Number n -> setValue(n.doubleValue());
                    default -> throw new IllegalStateException("Unexpected value: " + value);
                }
            }
            case "plot.color" ->
            {
                switch (value)
                {
                    case null -> plotColor = PLOT_COLOR;
                    case String s -> plotColor = Color.decode(s);
                    case Color c -> plotColor = c;
                    default -> throw new IllegalStateException("Unexpected value: " + value);
                }
            }
        }
    }

    private void setHighBound(double value)
    {
        if (value <= lowBound)
            throw new IllegalStateException("high bound is less than or equal to low bound: " + value);

        highBound = value;
    }

    private void setLowBound(double value)
    {
        if (value >= highBound)
            throw new IllegalStateException("low bound is greater than or equal to high bound: " + value);

        lowBound = value;
    }

    private void setFps(double value)
    {
        if (value <= 0 || value > 10)
            throw new IllegalStateException("bad fps: " + value);

        fps = value;
        timer.setDelay((int) (100 / fps));
        if (timer.isRunning())
            timer.restart();
    }

    private void setValue(double value)
    {
        plots[PLOT_COUNT - 1] = Math.max(lowBound, Math.min(highBound, value));
        count = PLOT_COUNT;
        repaint();
        if (!timer.isRunning())
            timer.start();
    }

    private void refresh(ActionEvent e)
    {
        if ((System.currentTimeMillis() - lastTick) * fps >= 1000)
        {
            repaint();
            double[] tmp = (plots == plots1) ? plots2 : plots1;
            if (count > 0)
            {
                Arrays.fill(tmp, 0);
                System.arraycopy(plots, 1, tmp, 0, count - 1);
                plots = tmp;
                count--;
            }
            if (count == 0)
            {
                timer.stop();
            }
            lastTick = System.currentTimeMillis();
        }
    }

    @Override
    protected void paintComponent(Graphics g)
    {
        super.paintComponent(g);

        Graphics2D g2d = (Graphics2D) g;
        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);

        FontMetrics fm = g2d.getFontMetrics();
        String hb = String.format("%.1f", highBound);
        String lb = String.format("%.1f", lowBound);

        int x0 = 40, y0 = 88;
        g2d.setPaint(FOREGROUND);
        g2d.drawLine(x0, y0, x0, y0 - Y_AXIS_SIZE);
        g2d.drawLine(x0, y0, x0 + X_AXIS_SIZE, y0);
        g2d.drawString(hb, x0 - fm.stringWidth(hb) - 2, y0 - Y_AXIS_SIZE);
        g2d.drawString(lb, x0 - fm.stringWidth(lb) - 2, y0);

        g2d.setPaint(plotColor);
        Stroke stroke = new BasicStroke(PLOT_WIDTH, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER);
        g2d.setStroke(stroke);
        double diff = highBound - lowBound;
        y0 = y0 - 1; // 避免与X轴重叠产生视觉重影
        for (int i = 0; i < PLOT_COUNT; i++)
        {
            if (plots[i] > 0)
            {
                int x = x0 + PLOT_GAP * (i + 1);
                int y = (int) (y0 - Y_AXIS_SIZE * Math.min(plots[i], diff) / diff) + 1; // 避免超出Y轴上界
                g2d.drawLine(x, y0, x, y);
            }
        }
    }
}
